#include "lock.h"
#include "time.h"
#include "stdio.h"
#include "sched.h"
#include "queue.h"
#include "screen.h"
#include "syscall.h"

pcb_t pcb[NUM_MAX_TASK];

/* current running task PCB */
pcb_t *current_running;

/* global process id */
pid_t process_id = 1;

/* kernel stack ^_^ */
#define NUM_KERNEL_STACK 20

static uint64_t kernel_stack[NUM_KERNEL_STACK];
static int kernel_stack_count;

static uint64_t user_stack[NUM_KERNEL_STACK];
static int user_stack_count;

/* used to fetch kernel_state error */
uint64_t *error = (uint64_t *)0x123456;

void init_stack()
{
}

uint64_t new_kernel_stack()
{
}

uint64_t new_user_stack()
{
}

static void free_kernel_stack(uint64_t stack_addr)
{
}

static void free_user_stack(uint64_t stack_addr)
{
}

/* Process Control Block */
void set_pcb(pid_t pid, pcb_t *pcb, task_info_t *task_info)
{
}

/* ready queue to run */
queue_t ready_queue;

/* block queue to wait */
queue_t block_queue;

/* sleeping queue to be waken */
queue_t sleeping_queue;

static void check_sleeping()
{
    uint32_t current_time = get_timer();
    if (queue_is_empty(&sleeping_queue))
        return;

    pcb_t *temp = (pcb_t *)(sleeping_queue.head);
    while (temp != NULL)
    {
        if (temp->sleeping_ddl < current_time)
        {
            queue_remove(&sleeping_queue, temp);
            temp->status = TASK_READY;
            queue_push(&ready_queue, temp);
        }
        temp = (pcb_t *)temp->next;
    }
    return;
}

void init_priority(void)
{
    if (current_running->priority <= 0)
        current_running->priority = current_running->init_priority;

    if (!queue_is_empty(&ready_queue))
    {
        pcb_t *temp;
        temp = ((pcb_t *)(ready_queue.head));
        while (temp != ((pcb_t *)(ready_queue.tail)))
        {
            temp->priority = temp->init_priority;
            temp = ((pcb_t *)(temp->next));
        }
        temp->priority = temp->init_priority;
    }
}

void scheduler(void)
{
    if (current_running->kernel_state == 1)
    {
        // save cursor
        current_running->cursor_x = screen_cursor_x;
        current_running->cursor_y = screen_cursor_y;
        
        // change the current running task into the status ready, then push it into ready_queue
        if (current_running->pid != 0 && current_running->status == TASK_RUNNING)
        {
            current_running->status = TASK_READY;
            queue_push(&ready_queue, current_running);
        }

        // check sleeping or wait task awake when ready_queue is empty
        check_sleeping();
        while (queue_is_empty(&ready_queue))
        {
            if (queue_is_empty(&sleeping_queue))
                break;
            check_sleeping();
        }
        
        // --------------------------------------- Scheduling without priority -------------------------------------
        // get the first task in ready_queue and change its status into running
        //current_running = queue_dequeue(&ready_queue);
        

        // --------------------------------------- Prioritized scheduling ------------------------------------------
        // init priority
        pcb_t *temp;
        int all_zero = 1;
        temp = ((pcb_t *)(ready_queue.head));
        while (temp != ((pcb_t *)(ready_queue.tail)) || temp->priority != 0)
        {
            if (temp->priority > 0)
            {
                all_zero = 0;
                break;
            }
            if (temp != ((pcb_t *)(ready_queue.tail)))
                temp = ((pcb_t *)(temp->next));
        }

        if (all_zero == 1)
        {
            init_priority();
        }

        // switch task while its priority no less than next task's
        temp = ((pcb_t *)(ready_queue.head));
        while (temp != ((pcb_t *)(ready_queue.tail)) && (temp->priority < ((pcb_t *)(temp->next))->priority))
        {
            temp = ((pcb_t *)(temp->next));
        }
        temp->priority--;
        current_running = temp;
        current_running->status = TASK_RUNNING;
        queue_remove(&ready_queue, temp);

        // re-load cursor
        screen_cursor_x = current_running->cursor_x;
        screen_cursor_y = current_running->cursor_y;
    }
    else
    {
        *error = 0;
    }
}

void do_sleep(uint32_t sleep_time)
{
    if (current_running->kernel_state == 1)
    {
        if (current_running->status == TASK_RUNNING)
        {
            current_running->status = TASK_SLEEPING;
            current_running->sleeping_ddl = get_timer() + sleep_time;
            current_running->priority = current_running->init_priority;
            queue_push(&sleeping_queue, current_running);
        }
        do_scheduler();
    }
    else
    {
        *error = 0;
    }
}

void do_exit(void)
{
}

void do_block(queue_t *queue)
{
    if (current_running->kernel_state == 1)
    {
      if (current_running->status == TASK_RUNNING)
      {
          current_running->status = TASK_BLOCKED;
          queue_push(queue, current_running);
      }
      // store current context and fetch the ready task
      do_scheduler();
    }
    else
    {
        *error = 0;
    }
}

void do_unblock_one(queue_t *queue)
{
    if (current_running->kernel_state == 1)
    {
        pcb_t *unblock_p;
        unblock_p = queue_dequeue(queue);
        unblock_p->priority = unblock_p->init_priority;
        unblock_p->status = TASK_READY;
        queue_push(&ready_queue, unblock_p);
    }
    else
    {
        *error = 0;
    }
}

void do_unblock_all(queue_t *queue)
{
    if (current_running->kernel_state == 1)
    {
        while (!queue_is_empty(queue))
        {
            do_unblock_one(queue);
        }
    }
    else
    {
        *error = 0;
    }
}

int do_spawn(task_info_t *task)
{
}

int do_kill(pid_t pid)
{
}

int do_waitpid(pid_t pid)
{
}

// process show
void do_process_show()
{
}

pid_t do_getpid()
{
}